
local Window = commonlib.inherit(commonlib.ToolBase);

function Window:ctor()
    self.m_root_watch_object = commonlib.NewRootWatchObject();
    self.m_env = self:NewEnv();
    self.m_window = nil;
end

function Window:GetEnv()
    return self.m_env;
end

function Window:NewEnv(watch_object, inherit_env)
    watch_object = watch_object or self.m_root_watch_object;

    local env = setmetatable({}, {
        __index = function (_, key)
            -- print("__index" , _, key);
            local value = watch_object:Get(key);
            if (value == nil and inherit_env ~= nil) then return inherit_env[key] end
            return value;
        end,
        __newindex = function (_, key, val)
            -- print("__newindex" , _, key, val);
            watch_object:Set(key, val);
        end
    });

    rawset(env, "print", _G.print);
    rawset(env, "tostring", _G.tostring);
    rawset(env, "type", _G.type);
    rawset(env, "io", _G.io);
    rawset(env, "file", _G.file);
    rawset(env, "math", _G.math);
    rawset(env, "os", _G.os);
    rawset(env, "string", _G.string);
    rawset(env, "table", _G.table);
    rawset(env, "utf8", _G.utf8);

    rawset(env, "commonlib", commonlib);
    rawset(env, "tag_templates", {});
    rawset(env, "inherit_env", inherit_env);
    rawset(env, "root_watch_object", self.m_root_watch_object);
    rawset(env, "watch_object", watch_object);
    rawset(env, "set", function(key, value) watch_object:Set(key, value) end);
    rawset(env, "get", function(key) watch_object:Get(key) end);
    rawset(env, "close", function() self:Close() end);

    -- C++ callback
    rawset(env, "refresh", function() self:Refresh() end);
    rawset(env, "watch", function(expr, callback) self:Watch(expr, callback) end);
    rawset(env, "dostring", function(script_text) return self:DoString(script_text) end);
    rawset(env, "dofile", function(script_file) return self:DoFile(script_file) end);
    rawset(env, "newenv", function() return self:NewEnv(watch_object:NewInheritWatchObject(), env) end);
    rawset(env, "get_template_by_tagname", function (tagname) return self:GetTemplateByTagName(tagname) end);

    return env;
end

function Window:Init(options, G)
    options = options or {};

    local env = self:GetEnv();
    if (type(G) == "table") then
        for key, value in pairs(G) do
            rawset(env, key, value);
        end
    end

    local root_template = "";
    if (options.template) then
        root_template = options.template;
    elseif (options.url) then
        root_template = commonlib.GetFileText(options.url);
    end

    rawset(env, "template", root_template);

    self.m_window = commonlib.LuaWindow(env);

    return self;
end

function Window:GetTemplateByTagName(tagname)
    local env = self:GetEnv();
    local lower_tagname = string.lower(tagname);

    while (env ~= nil) do
        local tag_templates = rawget(env, "tag_templates");
        local template = tag_templates[lower_tagname];
        if (template ~= nil) then return template end
        env = rawget(env, "inherit_env");
    end

    return nil;
end

function Window:Watch(expr, callback)
    local env = self:GetEnv();
    local watch_object = rawget(env, "watch_object");
    watch_object:NewWatchExpr(expr, commonlib.xpcall_callback(function(value)
        if (type(callback) == "function") then return callback(value) end
        if (type(callback) == "userdata") then callback:CallVoid_Ref(value) end
    end));
end

function Window:Refresh()
    self.m_root_watch_object:TriggerWatchExprCallback();
end

function Window:DoString(script_text)
    return commonlib.dostring(script_text, self:GetEnv());
end

function Window:DoFile(script_file)
    return commonlib.dostring(commonlib.GetScriptTextByFilePath(script_file), self:GetEnv(), script_file);
end

function Window:Show()
    self.m_window:Show();
end

function Window:Close()
    local OnClose = self:GetEnv().OnClose;
    if (type(OnClose) == "function") then OnClose() end
    self.m_window:Close();
    collectgarbage();
end


commonlib.ShowWindow = function(options, G)
    local window = Window:new():Init(options, G);
    window:Show();
    return window;
end

return Window;